//@author A0100976E
package tasky.logic;

import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.joda.time.DateTime;
import org.joda.time.DateTimeComparator;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;
import org.joda.time.format.DateTimeParser;

import tasky.commons.Task;
import tasky.history.History;
import tasky.storage.Storage;

public class SearchCommand implements Command {

	public ArrayList<Task> tasks;
	public AddParser addParser;
	public Storage storage;
	public History history;
	public Logger logger;
	private boolean isError = true;
	String data;
	String errorMessage;
	String output;

	// Magic Strings for different search mode
	private static final String SEARCH_DATE = "-date";
	private static final String SEARCH_FROM = "-from";
	private static final String SEARCH_TO = "-to";
	private static final String SEARCH_PRIORITY = "-!";
	private static final String SEARCH_LABEL = "-label";
	private static final String SEARCH_DONE = "-done";
	private static final String SEARCH_UNDONE = "-undone";
	private static final String SEARCH_TODAY = "-today";
	private static final String SEARCH_TOMORROW = "-tomorrow";

	private static final int BASEINDEX = 1;
	private static final String EMPTY = "";
	private static final String SPACING = " ";
	private static final String DESIRED_TASK = "%d. %s\n";
	private static final String ERROR_INVALID_DATE = "Date entered is invalid.\n";

	private static final String SEARCH_BY_DEADLINE = "Searching tasks that ends on: %s\n";
	private static final String SEARCH_BY_START_DATE = "Searching tasks that starts from: %s\n";
	private static final String SEARCH_BY_END_DATE = "Searching tasks that ends on: %s\n";
	private static final String SEARCH_BY_CONTENT = "Searching \"%s\"\n";
	private static final String SEARCH_BY_PRIORITY = "Searching tasks with priority:\n";
	private static final String SEARCH_BY_LABEL = "Searching tasks with label: %s\n";
	private static final String SEARCH_BY_DONE = "Searching tasks which are Done:\n";
	private static final String SEARCH_BY_UNDONE = "Searching tasks which are Undone:\n";
	private static final String SEARCH_TODAY_FORMAT = "Tasks which are due today:\n";
	private static final String SEARCH_TOMORROW_FORMAT = "Tasks which are due tomorrow:\n";
	private static final String SEARCH_TASK_DUE = "%d.[%02d:%02d] %s\n";
	private static final String SEARCH_NO_TASK_DUE = "-None-\n";
	private static final String SEARCH_ALL = "Searching for all tasks:\n";

	// date format
	private static final String DD_MMMM_YYYY = "dd/MMMM/yyyy";
	private static final String DD_MM_YYYY = "dd/MM/yyyy";
	private static final String DD_MMMM_YY = "dd/MMMM/yy";
	private static final String DD_MM_YY = "dd/MM/yy";

	public SearchCommand(ArrayList<Task> tasks, Storage storage,
			History history, Logger logger) {
		this.tasks = tasks;
		this.addParser = new AddParser();
		this.storage = storage;
		this.history = history;
		this.logger = logger;
		errorMessage = EMPTY;
	}

	@Override
	public void acceptInput(String data) {
		this.data = data;

	}

	@Override
	public void execute() {
		String searchMode = getSearchMode(data);
		String searchResults;
		switch (searchMode) {
		case SEARCH_DATE:
			searchResults = searchDate(data);
			break;
		case SEARCH_FROM:
			searchResults = searchFrom(data);
			break;
		case SEARCH_TO:
			searchResults = searchTo(data);
			break;
		case SEARCH_TODAY:
			searchResults = searchToday();
			break;
		case SEARCH_TOMORROW:
			searchResults = searchTomorrow();
			break;
		case SEARCH_PRIORITY:
			searchResults = searchPriority();
			break;
		case SEARCH_LABEL:
			searchResults = searchLabel(data);
			break;
		case SEARCH_DONE:
			searchResults = searchDone();
			break;
		case SEARCH_UNDONE:
			searchResults = searchUndone();
			break;
		default:
			searchResults = searchContent(data);
		}

		output = searchResults;

	}

	@Override
	public String save() throws IOException {
		return storage.writeToStorage(tasks);
	}

	@Override
	public String outputToUser() {
		return output;
	}

	private String searchDate(String data) {
		DateTime searchDate = null;
		isError = false;
		try {
			searchDate = obtainDate(data);
		} catch (Exception e) {
			errorMessage = e.getMessage();
			logger.log(Level.INFO, errorMessage);
			e.printStackTrace();
			isError = true;
		}
		if (isError) {
			return errorMessage;
		} else {
			DateTimeFormatter formatter = DateTimeFormat.forPattern(DD_MM_YYYY);
			StringBuilder searchDeadineBuilder = new StringBuilder();
			searchDeadineBuilder.append(String.format(SEARCH_BY_DEADLINE,
					formatter.print(searchDate)));
			searchDeadineBuilder
					.append(getContentsOfDueDate(searchDate, false));
			return searchDeadineBuilder.toString();
		}

	}

	private String searchFrom(String data) {
		DateTime searchFrom;
		DateTimeComparator compareDate = DateTimeComparator
				.getDateOnlyInstance();
		boolean taskDue = false;
		StringBuilder tasksDue = new StringBuilder();
		DateTime startDate;
		int hour;
		int minutes;

		try {
			searchFrom = obtainDate(data);
		} catch (ParseException e) {
			errorMessage = e.getMessage();
			logger.log(Level.INFO, errorMessage);
			e.printStackTrace();
			return errorMessage;
		}

		for (Task task : tasks) {
			startDate = task.getStart();
			if (startDate != null) {
				hour = startDate.getHourOfDay();
				minutes = startDate.getMinuteOfHour();
				if (compareDate.compare(startDate, searchFrom) == 0) {
					tasksDue.append(String.format(SEARCH_TASK_DUE,
							tasks.indexOf(task) + BASEINDEX, hour, minutes,
							task.getContent()));
					taskDue = true;
				}
			}
		}
		if (taskDue) {
			output = tasksDue.toString();
		} else {
			output = SEARCH_NO_TASK_DUE;
		}

		isError = false;
		DateTimeFormatter formatter = DateTimeFormat.forPattern(DD_MM_YYYY);
		StringBuilder searchFromBuilder = new StringBuilder();
		searchFromBuilder.append(String.format(SEARCH_BY_START_DATE,
				formatter.print(searchFrom)));
		searchFromBuilder.append(output);
		return searchFromBuilder.toString();
	}

	private String searchTo(String data) {
		DateTime searchTo;
		try {
			searchTo = obtainDate(data);
		} catch (ParseException e) {
			errorMessage = e.getMessage();
			logger.log(Level.INFO, errorMessage);
			e.printStackTrace();
			return errorMessage;
		}
		isError = false;
		DateTimeFormatter formatter = DateTimeFormat.forPattern(DD_MM_YYYY);
		StringBuilder searchToBuilder = new StringBuilder();
		searchToBuilder.append(String.format(SEARCH_BY_END_DATE,
				formatter.print(searchTo)));
		searchToBuilder.append(getContentsOfDueDate(searchTo, true));
		return searchToBuilder.toString();
	}

	private String searchToday() {
		StringBuilder searchTodayTasks = new StringBuilder();
		searchTodayTasks.append(SEARCH_TODAY_FORMAT);
		DateTime todayDate = new DateTime();
		searchTodayTasks.append(getContentsOfDueDate(todayDate, false));
		isError = false;
		return searchTodayTasks.toString();
	}

	private String searchTomorrow() {
		StringBuilder searchTomorrowTasks = new StringBuilder();
		searchTomorrowTasks.append(SEARCH_TOMORROW_FORMAT);
		DateTime todayDate = new DateTime();
		DateTime tomorrowDate = todayDate.plusDays(1);
		searchTomorrowTasks.append(getContentsOfDueDate(tomorrowDate, false));
		isError = false;
		return searchTomorrowTasks.toString();
	}

	private String searchPriority() {
		boolean noPrioritisedTask = true;
		isError = false;
		StringBuilder obtainAllTasks = new StringBuilder();

		obtainAllTasks.append(SEARCH_BY_PRIORITY);

		for (Task task : tasks) {
			if (task.isPriority()) {
				obtainAllTasks.append(String.format(DESIRED_TASK,
						tasks.indexOf(task) + BASEINDEX, task.getContent()));
				noPrioritisedTask = false;
			}
		}

		if (!noPrioritisedTask) {
			return obtainAllTasks.toString();
		} else {
			obtainAllTasks.append(SEARCH_NO_TASK_DUE);
			return obtainAllTasks.toString();
		}
	}

	private String searchDone() {

		boolean noDoneTask = true;
		isError = false;
		StringBuilder obtainAllTasks = new StringBuilder();

		obtainAllTasks.append(SEARCH_BY_DONE);

		for (Task task : tasks) {
			if (task.isDone()) {
				obtainAllTasks.append(String.format(DESIRED_TASK,
						tasks.indexOf(task) + BASEINDEX, task.getContent()));
				noDoneTask = false;
			}
		}

		if (!noDoneTask) {
			isError = false;
			return obtainAllTasks.toString();
		} else {
			obtainAllTasks.append(SEARCH_NO_TASK_DUE);
			return obtainAllTasks.toString();
		}
	}

	private String searchUndone() {
		boolean noUndoneTask = true;
		isError = false;
		StringBuilder obtainAllTasks = new StringBuilder();

		obtainAllTasks.append(SEARCH_BY_UNDONE);

		for (Task task : tasks) {
			if (!task.isDone()) {
				obtainAllTasks.append(String.format(DESIRED_TASK,
						tasks.indexOf(task) + BASEINDEX, task.getContent()));
				noUndoneTask = false;
			}
		}

		if (!noUndoneTask) {
			return obtainAllTasks.toString();
		} else {
			return obtainAllTasks.append(SEARCH_NO_TASK_DUE).toString();
		}
	}

	private String searchLabel(String data) {
		boolean noLabelTask = true;
		isError = false;
		StringBuilder obtainAllTasks = new StringBuilder();
		String label = getLabel(data);
		label.trim();
		obtainAllTasks.append(String.format(SEARCH_BY_LABEL, label));

		for (Task task : tasks) {
			if (task.getLabel() == null) {
			} else if (task.getLabel().equals(label)) {
				obtainAllTasks.append(String.format(DESIRED_TASK,
						tasks.indexOf(task) + BASEINDEX, task.getContent()));
				noLabelTask = false;
			}
		}

		if (!noLabelTask) {
			return obtainAllTasks.toString();
		}
		return obtainAllTasks.append(SEARCH_NO_TASK_DUE).toString();

	}

	private String searchContent(String data) {
		boolean noDesiredTask = true;
		isError = false;
		StringBuilder obtainAllTasks = new StringBuilder();
		data = data.trim();
		String content = data.trim();
		obtainAllTasks.append(searchHeader(content));

		for (Task task : tasks) {
			if (task.getContent() == null) {
			} else if (task.getContent().contains(content)) {
				obtainAllTasks.append(String.format(DESIRED_TASK,
						tasks.indexOf(task) + BASEINDEX, task.getContent()));
				noDesiredTask = false;
			}
		}

		if (!noDesiredTask) {
			return obtainAllTasks.toString();
		} else {
			obtainAllTasks.append(SEARCH_NO_TASK_DUE);
			return obtainAllTasks.toString();
		}
	}

	private String searchHeader(String content) {
		if (content.equals(EMPTY)) {
			return SEARCH_ALL;
		} else {
			return String.format(SEARCH_BY_CONTENT, content);
		}
	}

	private DateTime obtainDate(String data) throws ParseException {

		DateTimeParser[] parsers = {

		DateTimeFormat.forPattern(DD_MM_YY).getParser(),
				DateTimeFormat.forPattern(DD_MMMM_YY).getParser(),
				DateTimeFormat.forPattern(DD_MM_YYYY).getParser(),
				DateTimeFormat.forPattern(DD_MMMM_YYYY).getParser() };

		DateTimeFormatter formatter = new DateTimeFormatterBuilder().append(
				null, parsers).toFormatter();
		DateTime date = null;
		try {
			date = formatter.parseDateTime(getUserData(data));
		} catch (Exception e) {
			e.printStackTrace();
			throw new ParseException(ERROR_INVALID_DATE, 0);
		}
		return date;

	}

	private String getLabel(String data) {
		return omitFirstWord(data);
	}

	private String omitFirstWord(String sentence) {

		String[] words;
		String trimmedSentence;
		trimmedSentence = sentence.trim();

		words = trimmedSentence.split(SPACING, 2);
		if (words.length == 2) {
			return words[1];
		} else {
			return EMPTY;
		}

	}

	private String getUserData(String userInput) {
		return omitFirstWord(userInput);
	}

	private String getSearchMode(String userData) {
		return obtainFirstWord(userData);
	}

	private String obtainFirstWord(String sentence) {

		String[] words;
		String trimmedSentence;
		trimmedSentence = sentence.trim();

		words = trimmedSentence.split(SPACING);

		return words[0];

	}

	@Override
	public boolean isError() {
		return isError;
	}

	public int getNumOfTasksToday(DateTime todayDate) {

		int counter = 0;

		DateTimeComparator compareDate = DateTimeComparator
				.getDateOnlyInstance();
		for (Task task : tasks) {
			if (sameEndDate(todayDate, compareDate, task)
					|| sameDeadline(todayDate, compareDate, task)) {
				counter++;
			}
		}
		return counter;
	}

	private boolean sameDeadline(DateTime todayDate,
			DateTimeComparator compareDate, Task task) {
		return task.getDeadline() != null && compareDate.compare(
				task.getDeadline(), todayDate) == 0;
	}

	private boolean sameEndDate(DateTime todayDate,
			DateTimeComparator compareDate, Task task) {
		return task.getEnd() != null && compareDate.compare(task.getEnd(),
				todayDate) == 0;
	}

	public String getContentsOfDueDate(DateTime todayDate, boolean searchEndDate) {
		DateTimeComparator compareDate = DateTimeComparator
				.getDateOnlyInstance();
		boolean taskDue = false;

		StringBuilder tasksDue = new StringBuilder();
		DateTime dueDate;
		int hour;
		int minutes;
		for (Task task : tasks) {
			dueDate = null;
			if (task.getDeadline() != null && !searchEndDate) {
				dueDate = task.getDeadline();
			} else if (task.getEnd() != null) {
				dueDate = task.getEnd();
			}
			if (dueDate != null) {
				hour = dueDate.getHourOfDay();
				minutes = dueDate.getMinuteOfHour();
				if (compareDate.compare(dueDate, todayDate) == 0) {
					tasksDue.append(String.format(SEARCH_TASK_DUE,
							tasks.indexOf(task) + BASEINDEX, hour, minutes,
							task.getContent()));
					taskDue = true;
				}
			}
		}
		if (taskDue) {
			return tasksDue.toString();
		} else {
			return SEARCH_NO_TASK_DUE;
		}

	}

}
